#pragma once
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <openssl/ssl.h>
#include <openssl/crypto.h>
#include <openssl/err.h>
#include <openssl/stack.h>

#include <memory>

namespace umtl
{
	class ecies
	{
	public:
		#pragma region type define

		typedef unsigned __int64 uint64_t;

		struct secure_head_t 
		{
			struct length_t{
				uint64_t key;
				uint64_t mac;
				uint64_t orig;
				uint64_t body;
			} length;
		};

		typedef char * secure_t;

		template<class _Ty>
		struct cryptex_delete
		{
			typedef cryptex_delete<_Ty> _Myt;

			cryptex_delete() throw() {}

// 			template<typename _Ty2>
// 			cryptex_delete(const cryptex_delete< _Ty2 > &, typename enable_if< is_convertible< _Ty2 * , _Ty * >::value, void>::type ** = 0) throw()
// 			{}

			void operator()(_Ty *ptr) const throw()
			{
				static_assert(0 < sizeof (_Ty),
					"can't delete an incomplete type");
				if( ptr )
					secure_free(ptr);
			}
		};

		template<class _Ty>
		class ec_key_delete
		{
		public:
			typedef ec_key_delete<_Ty> _Myt;

			ec_key_delete() _NOEXCEPT {}

// 			template<class _Ty2>
// 			ec_key_delete(const ec_key_delete<_Ty2>&,
// 				typename enable_if<is_convertible<_Ty2 *, _Ty *>::value,
// 				void>::type ** = 0) _NOEXCEPT
// 			{}

			void operator()(_Ty *ptr) const _NOEXCEPT
			{
				//static_assert(0 < sizeof (_Ty),
				//	"can't delete an incomplete type");
				if( ptr )
					ecies_key_free(ptr);
			}
		};

		template<class _Ty>
		struct hex_key_delete
		{
			typedef hex_key_delete<_Ty> _Myt;

			hex_key_delete() _NOEXCEPT {}

// 			template<class _Ty2>
// 			hex_key_delete(const hex_key_delete<_Ty2>&,
// 				typename enable_if<is_convertible<_Ty2 *, _Ty *>::value,
// 				void>::type ** = 0) _NOEXCEPT
// 			{}

			void operator()(_Ty *ptr) const _NOEXCEPT
			{
				static_assert(0 < sizeof (_Ty),
					"can't delete an incomplete type");
				if( ptr )
					OPENSSL_free(ptr);
			}
		};

		typedef std::unique_ptr< char, cryptex_delete<char> > cryptex_t;
		typedef std::unique_ptr< EC_KEY, ec_key_delete<EC_KEY> > ec_key_t;
		typedef std::unique_ptr< char, hex_key_delete<char> > hex_key_t;

		typedef const EVP_CIPHER *evp_cipher_t(void);
		typedef const EVP_MD *	evp_md_t(void);

		#pragma endregion
		
		static evp_cipher_t*	GetChipher() { return _cipher; }
		static evp_md_t*		GetHasher() { return _hasher; }
		static int				GetCurve() { return _curve; }

		static ecies&			Instance();
		
		~ecies();
		
		static ec_key_t&		create_keys();
		static char*			public_key();
		static char*			private_key();

		cryptex_t				ecies_encrypt(char* key, unsigned char *data, size_t length);
		unsigned char *			ecies_decrypt(char* key, cryptex_t& cryptex, size_t *length);

		#pragma region private

	private:

		ecies();

		static void secure_free(secure_t cryptex);
		static unsigned char * secure_key_data(cryptex_t& cryptex);
		static unsigned char * secure_mac_data(cryptex_t& cryptex);
		static unsigned char * secure_body_data(cryptex_t& cryptex);
		static uint64_t secure_key_length(cryptex_t& cryptex);
		static uint64_t secure_mac_length(cryptex_t& cryptex);
		static uint64_t secure_body_length(cryptex_t& cryptex);
		static uint64_t secure_orig_length(cryptex_t& cryptex);
		static uint64_t secure_total_length(cryptex_t& cryptex);
		static secure_t secure_alloc(uint64_t key, uint64_t mac, uint64_t orig, uint64_t body);

		void ecies_group_init(void);
		void ecies_group_free(void);
		EC_GROUP * ecies_group(void);

		static void ecies_key_free(EC_KEY *key);

		ec_key_t ecies_key_create(void);
		ec_key_t ecies_key_create_public_hex(char *hex);
		ec_key_t ecies_key_create_private_hex(char *hex);
		ec_key_t ecies_key_create_public_octets(unsigned char *octets, size_t length);

		static hex_key_t ecies_key_public_get_hex(EC_KEY *key);
		static hex_key_t ecies_key_private_get_hex(EC_KEY *key);

		EC_GROUP *eliptic_;
		static int _curve;
		static evp_cipher_t* _cipher;
		static evp_md_t* _hasher;

		ec_key_t ec_key_;
		hex_key_t public_key_;
		hex_key_t private_key_;

		#pragma endregion
	};

}